/*
* Copyright (C) 2010-2012 Magnus Skjegstad
* 
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
* 
* The latest version of this library can be downloaded from http://mist-pubsub.googlecode.com
* 
*/
package com.skjegstad.mist2.storage;

import com.skjegstad.mist2.datatypes.DataElement;
import com.skjegstad.mist2.exceptions.MistException;
import com.skjegstad.mist2.exceptions.MistHashCollisionException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 * @author Magnus Skjegstad
 */
public abstract class DataElementStore {
    final ConcurrentHashMap<Long, DataElement> hashPerDataElement;

    public DataElementStore() {
        hashPerDataElement = new ConcurrentHashMap<Long, DataElement>();
    }
    
    /**
     * Store an element. Returns false if the element already exists or is expired. Throws exception
     * if there is a hash collision.
     *
     * @param dataElement data element to store
     * @return true when successful, otherwise false
     * @throws MistHashCollisionException thrown when a different dataelement with the same hash is detected
     */
    protected synchronized boolean putDataElement(DataElement dataElement) throws MistHashCollisionException {
        if (dataElement.isExpired())
            return false;

        long h = dataElement.getHash();
        DataElement prevElement = hashPerDataElement.get(h);
        if (prevElement != null) {
            if (!prevElement.equals(dataElement)) { // this can be removed to speed things up, but it's useful for debugging
                throw new MistHashCollisionException("Hash collision detected on hash " + h);
            }
            // skip the element, return false
            return false;
        } else {
            hashPerDataElement.put(h, dataElement);
            return true; // no previous element
        }
    }

    public DataElement getDataElement(long hash) {
        return hashPerDataElement.get(hash);
    }

    public Collection<DataElement> getDataElements(Collection<Long> hashes) {
        ArrayList<DataElement> ret = new ArrayList();
        for (long l : hashes) {
            DataElement e = hashPerDataElement.get(l);
            if (e != null)
                ret.add(e);
        }
        return Collections.unmodifiableList(ret);
    }

    public Collection<Long> getDataElementHashes() {
        return Collections.unmodifiableSet(hashPerDataElement.keySet());
    }

    public Collection<Long> getExpired() {
        ArrayList<Long> expired = new ArrayList();

        // find expired elements and add hash to expired-list
        synchronized(hashPerDataElement) {
            for (Entry<Long, DataElement> e : hashPerDataElement.entrySet())
                if (e.getValue().isExpired())
                    expired.add(e.getKey());
        }

        return expired;
    }
    
    public Collection<Long> getInvalid() {
        ArrayList<Long> invalidated = new ArrayList();

        // find invalidated elements and add hash to invalidated-list
        synchronized(hashPerDataElement) {
            for (Entry<Long, DataElement> e : hashPerDataElement.entrySet())
                if (e.getValue().isInvalid())
                    invalidated.add(e.getKey());
        }

        return invalidated;
    }

    protected void removeAll(Collection<Long> remove) {
        // remove all elements from remove-list
        if (!remove.isEmpty())
            for (Long l : remove)
                remove(l);
    }

    protected void remove(Long remove) {
        hashPerDataElement.remove(remove);
    }

    
}
